﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using Home.Haushalt.Lib.Element;
using System.Globalization;
using System.Windows.Forms;

namespace Home.Haushalt.Lib
{
    /// <summary>
    /// 
    /// </summary>
    public class Monthbalance
    {
        /// <summary></summary>
        public const string TURNUS_WEEKLY = "weekly";

        /// <summary></summary>
        public const string TURNUS_MONTHLY = "monthly";

        /// <summary></summary>
        private DataTable _postenTable;

        /// <summary></summary>
        private DataTable _dateTable;

        /// <summary></summary>
        public DateTime FromDate;

        /// <summary></summary>
        public DateTime ToDate;

        /// <summary></summary>
        public int Month { get; private set; }

        /// <summary></summary>
        public int Year { get; private set; }

        /// <summary></summary>
        private string _termin;

        /// <summary></summary>
        public double StartSaldo { get; internal set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="postenTable"></param>
        /// <param name="dateTable"></param>
        /// <param name="month"></param>
        /// <param name="year"></param>
        public Monthbalance(DataTable postenTable, DataTable dateTable, int month, int year)
        {
            _postenTable = postenTable;
            _dateTable = dateTable;
            Year = year;
            Month = month;
            FromDate = new DateTime(year, month, 1, 0, 0, 0);
            ToDate = new DateTime(year, month, DateTime.DaysInMonth(year, month), 23, 59, 59);

            _termin = year.ToString() + month.ToString("00");

            //Für die Tests, um Date-DataRows erstellen zu können
            if (_dateTable == null)
            {
                DataSet ds = new DataSet();
                ds.OpenSchema(typeof(Date));
                _dateTable = ds.Tables[0];
            }

            if (_postenTable == null)
            {
                DataSet ds = new DataSet();
                ds.OpenSchema(typeof(Posten));
                _postenTable = ds.Tables[0];
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public void DataBind()
        {
            //Alle bestehende relevante in Zeitrahmen passende Termine vorselektieren
            List<DataRow> monthExistsTerminDateRows = _dateTable.AsEnumerable().Where(dateRow =>
                dateRow.RowState!= DataRowState.Deleted && 
                !(bool)dateRow[Date.Fields.IsFree] &&
                dateRow[Date.Fields.Termin].ToString().StartsWith(FromDate.ToString("yyyyMM"))
                //(DateTime)dateRow[Date.Fields.Posting] <= ToDate &&
                //(DateTime)dateRow[Date.Fields.Posting] >= FromDate
                ).ToList();

            //Einzelne generierte Termine prüfen
            foreach (PostenDateItem createdDateItem in GetTurnusDateItems())
            {
                DataRow postenRow = createdDateItem.PostenRow;
                int turnusPeriode = postenRow[Posten.Fields.TurnusPeriode].ToInt();
                int postenId = postenRow[Posten.Fields.Id].ToInt();

                //Monatliches Turnus
                if (turnusPeriode.Equals(0))
                {
                    if (!monthExistsTerminDateRows.Any(dateRow => dateRow[Date.Fields.PostenId].ToInt().Equals(postenId)))
                    {
                        _dateTable.Rows.Add(createdDateItem.DateRows[0]);
                    }
                }

                //Wochentliches Turnus
                else if (turnusPeriode.Equals(1))
                {
                    GetMissingWeeklyDateRows(
                        createdDateItem.DateRows,
                        monthExistsTerminDateRows.Where(dateRow => dateRow[Date.Fields.PostenId].ToInt().Equals(postenId)).ToList()
                        ).ForEach(dateRow => _dateTable.Rows.Add(dateRow));
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="createdDateRows"></param>
        /// <param name="existsDateRows"></param>
        /// <returns></returns>
        private List<DataRow> GetMissingWeeklyDateRows(List<DataRow> createdDateRows, List<DataRow> existsDateRows)
        {
            List<DataRow> result = new List<DataRow>();

            int missingDateCount = createdDateRows.Count - existsDateRows.Count;

            //Kein einziges gefunden
            if (missingDateCount.Equals(createdDateRows.Count))
            {
                createdDateRows.ForEach(dateRow => result.Add(dateRow));
            }

            //Ein paar fehlen
            else if (missingDateCount > 0)
            {
                //Vermutete fehlende Termine finden (weil Turnus sich ändern könnte)
                List<string> presumingMissingDates = createdDateRows.Select(dateRow => dateRow[Date.Fields.Termin].ToString()).
                    Except(existsDateRows.Select(dateRow => dateRow[Date.Fields.Termin].ToString())).ToList();

                if (presumingMissingDates.Count.Equals(missingDateCount))
                //Richtige Anzahl fehlt. Nur fehlende hinzufügen
                {
                    presumingMissingDates.ForEach(termin =>
                        result.Add(createdDateRows.Where(dateRow => dateRow[Date.Fields.Termin].ToString() == termin).FirstOrDefault()));
                }
                else
                //Falsche Anzahl fehlt. Alle hinzufügen
                {
                    createdDateRows.ForEach(dateRow => result.Add(dateRow));
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private List<PostenDateItem> GetTurnusDateItems()
        {
            List<PostenDateItem> postendateItems = _postenTable.AsEnumerable().
                    Where(row =>
                        //Mit gesetzten Turnus
                        row[Posten.Fields.Turnus] != System.DBNull.Value && row[Posten.Fields.Turnus] != null && !row[Posten.Fields.Turnus].ToString().Trim().Equals(string.Empty) &&

                        //die in Vergangenheit beginnen
                        row[Posten.Fields.DateFrom].ToDateTime() < ToDate &&

                        //die in Zukunft enden
                        (row[Posten.Fields.DateTo] == System.DBNull.Value || row[Posten.Fields.DateTo].ToDateTime() > FromDate)

                   ).Select(row => GetTurnusDateItems(row)).Where(item => !item.DateRows.Count.Equals(0)).ToList();

            return postendateItems;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="turnusVar"></param>
        /// <returns></returns>
        private PostenDateItem GetTurnusDateItems(DataRow postenRow)
        {
            List<DateTime> dates = null;

            DateTime postenFrom = postenRow[Posten.Fields.DateFrom].ToDateTime();
            DateTime postenTo = postenRow[Posten.Fields.DateTo] == System.DBNull.Value ? DateTime.MaxValue : postenRow[Posten.Fields.DateTo].ToDateTime();

            switch (postenRow[Posten.Fields.TurnusPeriode].ToInt())
            {
                case 1:
                    dates = CheckWeekly(postenRow[Posten.Fields.Turnus].ToString(), postenFrom, postenTo);
                    break;

                case 0:
                    dates = CheckMonthly(postenRow[Posten.Fields.Turnus].ToString(), postenFrom, postenTo, postenRow[Posten.Fields.DateDay].ToInt());
                    break;
            }

            return CreateDateItem(postenRow, dates);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="turnusString"></param>
        /// <returns></returns>
        private List<DateTime> CheckMonthly(string turnusString, DateTime postenFrom, DateTime postenTo, int day)
        {
            List<DateTime> result = new List<DateTime>();

            DateTime date = GetDateTimeTry(Year, Month, day);

            if (
                //Entspricht dem Turnus
                turnusString.ToList().Contains(Month.ToString()) &&
                //Termin-Tag liegt in Posten-Gültigkeitsraum
                date >= postenFrom && date <= postenTo)
            {
                result.Add(GetDateTimeTry(FromDate.Year, FromDate.Month, day));
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="turnusString"></param>
        /// <param name="postenFrom"></param>
        /// <param name="postenTo"></param>
        /// <returns></returns>
        private List<DateTime> CheckWeekly(string turnusString, DateTime postenFrom, DateTime postenTo)
        {
            List<DateTime> result = new List<DateTime>();
            DateTime date = postenFrom > FromDate ? postenFrom : FromDate;
            postenTo = postenTo > ToDate ? ToDate : postenTo;

            List<int> turnusList = turnusString.ToList().Select(item => item.ToInt()).ToList();

            while (date <= postenTo)
            {
                if (turnusList.Contains((int)date.DayOfWeek))
                {
                    result.Add(date);
                }
                date = date.AddDays(1);
            }
            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="postenRow"></param>
        /// <param name="postingDates"></param>
        /// <returns></returns>
        private PostenDateItem CreateDateItem(DataRow postenRow, List<DateTime> postingDates)
        {
            List<DataRow> postingDateRows = new List<DataRow>();

            foreach (DateTime postingDate in postingDates)
            {
                DataRow dateRow = _dateTable.NewRow();
                postingDateRows.Add(dateRow);

                dateRow[Date.Fields.Posting] = postingDate;

                dateRow[Date.Fields.Active] = true;
                dateRow[Date.Fields.GroupId] = postenRow[Posten.Fields.GroupId];
                dateRow[Date.Fields.IsFree] = false;
                dateRow[Date.Fields.Name] = postenRow[Posten.Fields.Name];
                dateRow[Date.Fields.OpponentId] = postenRow[Posten.Fields.OpponentId];
                dateRow[Date.Fields.PostenId] = postenRow[Posten.Fields.Id];
                dateRow[Date.Fields.SaldotypId] = postenRow[Posten.Fields.SaldotypeId];
                dateRow[Date.Fields.Summe] = postenRow[Posten.Fields.Summe];
                dateRow[Date.Fields.Termin] = CreateTerminString(Year, Month, postenRow[Posten.Fields.TurnusPeriode].ToInt().Equals(0) ? 0 : postingDate.Day);
                dateRow[Date.Fields.TerminTag] = postenRow[Posten.Fields.DateDay];
            }

            return new PostenDateItem(postenRow, postingDateRows);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="year"></param>
        /// <param name="month"></param>
        /// <param name="day"></param>
        /// <returns></returns>
        private string CreateTerminString(int year, int month, int day)
        {
            return year.ToString("00") + month.ToString("00") + day.ToString("00");
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="year"></param>
        /// <param name="month"></param>
        /// <param name="dayVar"></param>
        /// <returns></returns>
        private DateTime GetDateTimeTry(int year, int month, object dayVar)
        {
            int maxDayInMonth = DateTime.DaysInMonth(year, month);
            int day = dayVar.ToInt();

            if (day > maxDayInMonth)
            {
                day = maxDayInMonth;
            }

            return new DateTime(year, month, day);
        }

        /// <summary>
        /// 
        /// </summary>
        public class PostenDateItem
        {
            /// <summary></summary>
            public DataRow PostenRow;
            /// <summary></summary>
            public List<DataRow> DateRows;
            /// <summary>
            /// 
            /// </summary>
            /// <param name="postenRow"></param>
            /// <param name="dateRows"></param>
            public PostenDateItem(DataRow postenRow, List<DataRow> dateRows)
            {
                PostenRow = postenRow;
                DateRows = dateRows;
            }
        }
    }
}
